package com.api.rc.lobby.domain;

import com.api.rc.lobby.message.MessageGenerator;
import com.google.common.base.Preconditions;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.socket.TextMessage;

import java.io.IOException;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * 游戏大厅房间
 *
 * @author RenQiang
 * @date 2021/7/13
 */
@Slf4j
@Getter
public class LobbyRoom implements Serializable {
    private static final long serialVersionUID = 728134468881398091L;

    private final long roomId;
    private final long hostUserId;
    private final Map<Integer, LobbyTeam> teamMap;
    private final ConcurrentHashMap<Long, LobbyPlayer> freeManMap;
    /**
     * 主持人
     */
    private final AtomicReference<LobbyPlayer> hostRef;
    /**
     * 玩家所属队伍
     */
    private final ConcurrentHashMap<Long, Integer> userTeamMap;

    public LobbyRoom(Long roomId, long hostUserId, int teamCount, int membersPerTeam) {
        this.roomId = roomId;
        this.hostUserId = hostUserId;
        Map<Integer, LobbyTeam> teamMap = IntStream.rangeClosed(1, teamCount)
                .mapToObj(no -> new LobbyTeam(no, membersPerTeam))
                .collect(Collectors.toMap(LobbyTeam::getNo, Function.identity()));
        this.teamMap = Collections.unmodifiableMap(teamMap);
        this.freeManMap = new ConcurrentHashMap<>(teamCount * membersPerTeam);
        this.hostRef = new AtomicReference<>();
        this.userTeamMap = new ConcurrentHashMap<>(teamCount * membersPerTeam);
    }

    public boolean isReady() {
        return this.teamMap.values().stream()
                .allMatch(LobbyTeam::isReady);
    }

    public boolean isFull() {
        int freePlayerSize = freeManMap.size();
        int teamedPlayerSize = teamMap.values().stream()
                .mapToInt(lobbyTeam -> lobbyTeam.getMemberMap().size())
                .sum();
        int roomCapacity = teamMap.values().stream()
                .mapToInt(LobbyTeam::getMaxMembers)
                .sum();
        return freePlayerSize + teamedPlayerSize >= roomCapacity;
    }

    public void addFreeMan(LobbyPlayer lobbyPlayer) {
        Preconditions.checkArgument(!isFull(), "房间人数已满");
        freeManMap.put(lobbyPlayer.getUserInfo().getId(), lobbyPlayer);
    }

    public void removePlayer(Long userId) {
        Integer teamNo = userTeamMap.get(userId);
        //玩家已加入队伍
        if (teamNo != null) {
            LobbyTeam lobbyTeam = teamMap.get(teamNo);
            lobbyTeam.removeMember(userId);
            userTeamMap.remove(userId);
            this.broadcastTeamDetail(teamNo);
        }
        //主持人
        else if (userId == hostUserId) {
            this.hostRef.set(null);
        } else {
            freeManMap.remove(userId);
        }
    }

    public List<LobbyPlayer> getAllPlayers() {
        List<LobbyPlayer> allPlayers = teamMap.values().stream()
                .flatMap(lobbyTeam -> lobbyTeam.getMemberMap().values().stream())
                .collect(Collectors.toList());
        allPlayers.addAll(freeManMap.values());
        return allPlayers;
    }

    public String summary() {
        int roomCapacity = teamMap.values().stream()
                .mapToInt(LobbyTeam::getMaxMembers)
                .sum();
        long readyCount = teamMap.values().stream()
                .flatMap(lobbyTeam -> lobbyTeam.getMemberMap().values().stream())
                .filter(LobbyPlayer::isReady)
                .count();
        return String.format("[roomId: %d, capacity: %d, freeMan: %d, teamed: %d, ready: %d, hostIn: %s]", roomId, roomCapacity, freeManMap.size(), userTeamMap.size(), readyCount, hostRef.get() != null);
    }

    public void changeUserTeam(Long userId, int teamNo) {
        LobbyPlayer lobbyPlayer;
        Integer beforeTeamNo = userTeamMap.get(userId);
        if (beforeTeamNo != null) {
            LobbyTeam lobbyTeam = teamMap.get(beforeTeamNo);
            lobbyPlayer = lobbyTeam.removeMember(userId);
            userTeamMap.remove(userId);
            freeManMap.put(userId, lobbyPlayer);
            this.broadcastTeamDetail(beforeTeamNo);
        }
        lobbyPlayer = freeManMap.remove(userId);
        LobbyTeam lobbyTeam = teamMap.get(teamNo);
        if (lobbyTeam.addMember(lobbyPlayer)) {
            userTeamMap.put(userId, teamNo);
            this.broadcastTeamDetail(teamNo);
        } else {
            freeManMap.put(userId, lobbyPlayer);
        }
    }

    private void broadcastTeamDetail(int teamNo) {
        List<LobbyPlayer> roomMembers = this.getAllPlayers();
        if (hostRef.get() != null) {
            roomMembers.add(hostRef.get());
        }
        for (LobbyPlayer lobbyPlayer : roomMembers) {
            LobbyTeam lobbyTeam = teamMap.get(teamNo);
            try {
                TextMessage textMessage = MessageGenerator.genTeamDetailMessage(roomId, lobbyTeam);
                lobbyPlayer.getSession().sendMessage(textMessage);
            } catch (IOException e) {
                log.error("发送WebSocket消息失败", e);
            }
        }
    }

    public void playerReady(Long userId) {
        Integer teamNo = userTeamMap.get(userId);
        if (teamNo != null) {
            LobbyTeam lobbyTeam = teamMap.get(teamNo);
            lobbyTeam.playerReady(userId);
            this.broadcastTeamDetail(teamNo);
        }
    }

    public void hostJoinIn(LobbyPlayer lobbyPlayer) {
        this.hostRef.set(lobbyPlayer);
    }
}
